import * as events from 'events';
import * as rs from 'redis';
import * as log from '../log/log';
import * as eventRedis from './event-redis';

/**
 *  redis的操作类，实现了字符串的速写，hash的读写，过去的设定等。
 *  如非必要，不需要实例化本例，可以调用 getRedis() 方法，会根据配置文件中的配置获取实例。
 * 
 * @class
 * @extends {events.EventEmitter}
 */
export class Redis extends events.EventEmitter{

    private client:rs.RedisClient;
    private db:number;
    private prefix:string;
    private noop = function(){};

    constructor(option:OptionClient){
        super();  
        this.client = rs.createClient(option.port,option.host,{"password":option.password});
        this.db = option.dbName;
        this.prefix = option.prefix;
        this.init();  
    }

    /**
     * 退出
     * 
     * @memberOf Redis
     */
    public quit(){
        this.client.quit();
        
    }

    
    /**
     *  派发事件，如果其他客户端连接了本服务器redis，将收到消息
     * 
     * @param {string} channel
     * @param {string} value
     * 
     * @memberOf Redis
     */
    public publish(channel:string,value:string){
        this.client.publish(channel,value);
    }
    
    /**
     *  订阅事件，接受其他客户的消息。
     * 
     * @param {string} channel
     * 
     * @memberOf Redis
     */
    public subscribe(channel:string){
        this.client.subscribe(channel);
    }

    private onReady(){
        this.emit(eventRedis.Event.ready);
    }

    private getPrefixKey(key:string|string[]){
        if(typeof key === 'object'){
            key.forEach((value,index,array)=>{
                if(value.indexOf(this.prefix)===-1){
                    array[index]= this.prefix + key;
                }
            })
        }else{
            key =  this.prefix + key;
        }
        return key;
    }

    /**
     *  设置键的过期时间
     * 
     * @param {*} key
     * @param {number} expired 秒,默认为1年
     * @returns
     * 
     * @memberOf Redis
     */
    public setKeyExpired(key:any,expired?:number){
        let newKey = this.getPrefixKey(key);
        let s = expired;
        if(!s)
            s = 365 * 86400;
        return this.client.expire(newKey,s);
    }

    /**
     *  添加hash对象,可以是js对象，符合规范即可。如果索引键存在会被新值替换。 获取hash格式的方法为getHash()
     * 
     * @param {string} key 索引键
     * @param {*} hash 对象格式为{"key1":"value1","key2":"value2"...}
     * @param {(err:Error, res:any)=>void} [callback]
     * 
     * @memberOf Redis
     */
    public setHash(key:string,hash:any,callback?:(err:Error, res:any)=>void,expired?:number){
        let newKey = this.getPrefixKey(key);
        if(!callback) callback = this.noop;
        this.client.hmset(newKey,hash,(err,res)=>{
            this.setKeyExpired(newKey,expired);
            callback(err,res);
        });
    }


    /**
     *  获取hash对象，返回json对象
     * 
     * @param {string} key
     * @param {(err:Error, data?:any)=>void} [callback]  查找不到data返回null
     * 
     * @memberOf Redis
     */
    public getHash(key:string,callback?:(err:Error, data?:any)=>void){
        let newKey = this.getPrefixKey(key);
        this.client.hgetall(newKey, function (err, data) {
            if (err) return callback(err);
            if (!data) return callback(null,null);
            var result = data;
            return callback(null, result);
        });
    }
    
    /**
     *  获得特定hash中的指定字段
     * 
     * @param {string} key
     * @param {string} field
     * @param {(err:Error, data?:any)=>void} [callback] 查找不到data返回null
     * 
     * @memberOf Redis
     */
    public getHashField(key:string,field:string,callback?:(err:Error, data?:any)=>void){
        let newKey = this.getPrefixKey(key);
        this.client.hget(newKey,field, function (err, data) {
            if (err) return callback(err);
            if (!data) return callback(null,null);
            var result = data;
            return callback(null, result);
        });
    }
    
    /**
     *  获取索引键中的所有键
     * 
     * @param {string} key
     * @param {(err:Error, data?:any)=>void} [callback]
     * 
     * @memberOf Redis
     */
    public getHashKeys(key:string,callback?:(err:Error, data?:any)=>void){
        let newKey = this.getPrefixKey(key);
        this.client.hkeys(newKey,callback);
    }

    
    /**
     *  删除索引键中的先关键值
     * 
     * @param {string} key
     * @param {string} field 删除单个deleteHash("key","a")，删除多个deleteHash("key",["a","b"])
     * @param {(err:Error, data?:any)=>void} [callback] 如果想获得参数成功数目，监听回调
     * @returns 执行是否成功
     * 
     * @memberOf Redis
     */
    public deleteHash(key:string,field:any,callback?:(err:Error, data?:any)=>void){
        let newKey = this.getPrefixKey(key);
        return this.client.hdel(newKey,field,(err,data)=>{
            callback(err,data);
        });
    }

    /**
     *  设置单个键值，将对象已字符串形式存储，对应的获取函数为 getString()
     *  如果有传入的字符串是对象，并且对象中的键值有频繁改变建议使用 setHash()
     * 
     * @param {string} key 键
     * @param {string} value 值，会自动转码为json字符串，不需要外部转码
     * @param {(err:Error, res:any)=>void} [callback] 回调返回，成功后，res="OK"
     * 
     * @memberOf Redis
     */
    public setString(key:string,value:string,callback?:(err:Error, res:any)=>void,expired?:number){
        let newKey = this.getPrefixKey(key);
        if(!callback) callback = this.noop;
        this.client.set(newKey,JSON.stringify(value),(err,res)=>{
            this.setKeyExpired(newKey,expired);
            callback(err,res);
        });
    }

    /**
     * 获取单个键值，返回js对象
     * 
     * @param {string} key
     * @param {(err:Error, data?:any)=>void} [callback]
     * 
     * @memberOf Redis
     */
    public getString(key:string,callback?:(err:Error, data?:any)=>void){
        
        let newKey = this.getPrefixKey(key);
        if (!callback) callback = this.noop;
        this.client.get(newKey, function (err, data) {
            if (err) return callback(err);
            if (!data) return callback(null,null);

            var result;
            data = data.toString();

            try {
                result = JSON.parse(data); 
            }
            catch (er) {
                return callback(er);
            }
            return callback(null, result);
        });
    }

    /**
     *  验证键是否存在，可输入多个键同时验证。
     *  单个 exists("test",(err,count)=>{}) 如果存在，count返回查找到的个数
     *  多个 exists(["test","test2"],(err,count)=>{}) count 返回匹配的个数，如果只匹配到一个，count返回1
     * 
     * @param {(string|string[])} key
     * @param {(err,count:number)=>void} callback
     * @returns {boolean}
     * 
     * @memberOf Redis
     */
    public exists(key:string|string[],callback:(err,count:number)=>void){
        let newKey = this.getPrefixKey(key);
        this.client.hexists("",)
        return this.client.exists(newKey,(err,data)=>{
            callback(err,data);
        });
    }

    private init(){

        if (typeof this.db !== 'number') {
            console.error('Warning: connect-redis expects a number for the "db" option');
        }
        this.client.on('connect',()=>this.onConnect());
        this.client.on('ready',()=>this.onReady());
        this.client.on("error",err=>this.onError(err));
        this.client.on("message",(channel, message)=>this.onMessage(channel, message));
        this.client.select(this.db);
    }

    private onConnect(){
        console.log("redis connect");
        this.client.select(this.db);
        this.emit(eventRedis.Event.connect);
    }


    private onError(err){
        try{
            console.log("redis - ",err.message);
            log.getLogger(log.type.redisLog).error(err);
            this.emit(eventRedis.Event.error,err);
        }catch(e){}
    }

    private onMessage(channel, message) {
        this.emit(eventRedis.Event.message,channel,message);
    }
    
}



export interface OptionClient{
    port:number,
    host:string,
    password?:string,
    dbName?:number,
    prefix?:string
}


